#pragma once

#include <algorithm>
#include <cassert>
#include <initializer_list>
#include <string>
#include <sstream>
#include <vector>


#include "utils/string_utils/string_utils.h"

namespace txdnn {
namespace backend {

enum class ParameterTypes {
  Define,
  Option,
};


struct KernelBuildParameter {
  ParameterTypes type;
  std::string name;
  std::string value;
};


class KernelBuildParams {
public:
  struct KBPInit {
    friend class KernelBuildParams;
    KBPInit(const std::string& name, const std::string& value = "") : data{ParameterTypes::Define, name, value} {}

    template<class TValue, class = decltype(std::to_string(std::declval<TValue>()))>
    KBPInit(const std::string& name, const TValue& value) : KBPInit(name, std::to_string(value)) {}

    // KBPInit(ParameterTypes::Option, const std::string& name, const std::string& value = "")
    //   : data{ParameterTypes::Option, name, value} {}

    // template<class TValue, class = decltype(std::to_string(std::declval<TValue>()))>
    // KBPInit(ParameterTypes::Option, const std::string& name, const TValue& value)
    //   : KBPInit(ParameterTypes::Option, name, std::to_string(value)) {}

  private:
    KernelBuildParameter data{};
  };

public:
  KernelBuildParams() = default;
  KernelBuildParams(const std::initializer_list<KBPInit>& defines) {
    options_.reserve(defines.size());
    for (const auto& define : defines) {
      assert(ValidateUniqueness(define.data.name));
      options_.push_back(define.data);
    }
  }
  KernelBuildParams& operator<<(const KernelBuildParams& other) {
    std::copy(other.options_.begin(), other.options_.end(), std::back_inserter(options_));
    return *this;
  }

  bool empty() const { return options_.empty(); }

  void define(const std::string& name, const std::string& value) {
    assert(ValidateUniqueness(name));
    options_.push_back({ParameterTypes::Define, name, value});
  }

  template<class TValue, class = decltype(std::to_string(std::declval<TValue>()))>
  void define(const std::string& name, const TValue& value) {
    define(name, std::to_string(value));
  }

  template <class TFor>
  std::string generatorFor(TFor&&) const {
    return TFor::Generate(options_);
  }

private:
  std::vector<KernelBuildParameter> options_ = {};

  bool ValidateUniqueness(const std::string& name) const {
    const auto eq = [=](const auto& item) { return item.name == name; };
    return std::find_if(options_.begin(), options_.end(), eq) == options_.end();
  }
};


static std::string GenerateDefines(const std::vector<KernelBuildParameter>& options,
                                   const std::string& prefix) {
  // std::string strs;
  std::vector<std::string> strs;

  auto add_options = [&prefix](const KernelBuildParameter& define) {
    std::ostringstream ss;

    ss << '-';
    if (define.type == ParameterTypes::Define) {
      ss << prefix;
    }
    ss << define.name;

    if (!define.value.empty()) {
      switch (define.type) {
        case ParameterTypes::Define: ss << '='; break;
        case ParameterTypes::Option: ss << ' '; break;
      }
      ss << define.value;
    }
    return ss.str();
  };

  std::transform(options.begin(), options.end(), std::back_inserter(strs), add_options);
  // TODO every element of options is same ? To checking
  return JoinStrings(strs, " ");
}

namespace kbp {
struct MainC {
  static std::string Generate(const std::vector<KernelBuildParameter>& options) {
    return GenerateDefines(options, "D");
  }
}; 
} // namespace kbp


} // namespace backend
} // namespace txdnn